from flask import Flask, request, redirect, send_from_directory, jsonify, Response
from werkzeug.exceptions import ClientDisconnected
import urllib.parse
from werkzeug.utils import secure_filename
import os
import re
import hashlib
import threading
import json
import time
import queue
from werkzeug.datastructures import FileStorage
import uuid

app = Flask(__name__)

# 文件存储和缓冲区配置
UPLOAD_FOLDER = '/var/www/html/Download'
MD5_FILE = os.path.join(UPLOAD_FOLDER, '.md5_cache.json')
CHUNK_SIZE = 1024 * 1024 * 4    # 4MB
BUFFER_SIZE = 1024 * 1024 * 16   # 16MB

# 全局变量
upload_progress = {}  # 存储上传进度的字典
md5_queue = queue.Queue()  # MD5计算队列

def secure_filename_with_chinese(filename):
    """安全的文件名处理，支持中文"""
    filename = re.sub(r'[<>:"/\\|?*]', '', filename)
    return filename

def load_md5_cache():
    """从文件加载MD5缓存"""
    try:
        if os.path.exists(MD5_FILE):
            with open(MD5_FILE, 'r') as f:
                return json.load(f)
    except Exception as e:
        print(f"加载MD5缓存失败: {e}")
    return {}

def save_md5_cache(cache):
    """保存MD5缓存到文件"""
    try:
        temp_file = MD5_FILE + '.tmp'
        with open(temp_file, 'w') as f:
            json.dump(cache, f)
        os.replace(temp_file, MD5_FILE)
    except Exception as e:
        print(f"保存MD5缓存失败: {e}")

def calculate_md5_background(file_path):
    """后台计算MD5并保存到缓存文件"""
    md5_hash = hashlib.md5()
    try:
        with open(file_path, "rb") as f:
            while True:
                chunk = f.read(CHUNK_SIZE)
                if not chunk:
                    break
                md5_hash.update(chunk)
        md5_value = md5_hash.hexdigest()
        
        cache = load_md5_cache()
        cache[os.path.basename(file_path)] = md5_value
        save_md5_cache(cache)
    except Exception as e:
        print(f"计算MD5失败: {e}")

def md5_worker():
    """后台工作线程，处理MD5计算队列"""
    while True:
        try:
            file_path = md5_queue.get()
            if file_path is None:
                break
            calculate_md5_background(file_path)
            md5_queue.task_done()
        except Exception as e:
            print(f"MD5工作线程错误: {e}")
            continue

def scan_existing_files():
    """扫描现有文件并添加到MD5计算队列"""
    cache = load_md5_cache()
    try:
        files = [f for f in os.listdir(UPLOAD_FOLDER) if not f.startswith('.')]
        for filename in files:
            file_path = os.path.join(UPLOAD_FOLDER, filename)
            if os.path.isfile(file_path) and filename not in cache:
                md5_queue.put(file_path)
    except Exception as e:
        print(f"扫描现有文件失败: {e}")

def get_file_md5(filename):
    """获取文件的MD5值"""
    cache = load_md5_cache()
    return cache.get(filename, "计算中...")

def get_file_size(file_path):
    """获取文件大小的人类可读格式"""
    try:
        size = os.path.getsize(file_path)
        for unit in ['B', 'KB', 'MB', 'GB']:
            if size < 1024:
                return f"{size:.1f}{unit}"
            size /= 1024
        return f"{size:.1f}TB"
    except Exception:
        return "未知大小"

class ProgressFileStorage(FileStorage):
    """文件上传进度跟踪类"""
    def __init__(self, file_obj, upload_id):
        super().__init__(file_obj)
        self.upload_id = upload_id
        self.total_bytes = 0
        self.bytes_read = 0
        self.last_update = time.time()
        self.start_time = time.time()

    def read(self, size=-1):
        chunk = super().read(size)
        self.bytes_read += len(chunk)
        
        current_time = time.time()
        if current_time - self.last_update >= 0.1:
            if self.total_bytes > 0:
                progress = (self.bytes_read / self.total_bytes) * 100
                elapsed_time = current_time - self.start_time
                speed = self.bytes_read / elapsed_time if elapsed_time > 0 else 0
                remaining = (self.total_bytes - self.bytes_read) / speed if speed > 0 else 0
                
                upload_progress[self.upload_id] = {
                    'progress': progress,
                    'speed': speed,
                    'remaining': remaining
                }
            self.last_update = current_time
        return chunk

    def save(self, dst, buffer_size=BUFFER_SIZE):
        if not self.total_bytes:
            self.total_bytes = self.content_length
        return super().save(dst, buffer_size)

@app.route('/upload-progress/<upload_id>')
def get_upload_progress(upload_id):
    """获取上传进度"""
    progress = upload_progress.get(upload_id, {'progress': 0, 'speed': 0, 'remaining': 0})
    return jsonify(progress)

@app.route('/')
def upload_form():
    """主页面"""
    try:
        files = sorted(os.listdir(UPLOAD_FOLDER))
        files = [f for f in files if not f.startswith('.')]
        files_html = '<br>'.join([
            f'<div class="file-item" title="MD5: {get_file_md5(f)}">'
            f'<div class="file-main">'
            f'<div class="file-icon">📄</div>'
            f'<div class="file-info">'
            f'<div class="file-name"><a href="/download/{f}">{f}</a></div>'
            f'<div class="file-details">'
            f'<span class="file-size">{get_file_size(os.path.join(UPLOAD_FOLDER, f))}</span>'
            f'<span class="file-md5">MD5: {get_file_md5(f)}</span>'
            f'</div>'
            f'</div>'
            f'<div class="file-actions">'
            f'<a href="/delete/{f}" class="delete-btn" onclick="return confirm(\'确定要删除这个文件吗？\')">删除</a>'
            f'</div>'
            f'</div>'
            f'</div>'
            for f in files
        ])
    except Exception as e:
        files_html = f'<div class="error">读取文件列表失败: {str(e)}</div>'
    
    return f'''
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>文件上传系统</title>
        <style>
            * {{ box-sizing: border-box; }}
            body {{ 
                padding: 20px;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                background-color: #f5f7fa;
                margin: 0;
                color: #2c3e50;
            }}
            .container {{ 
                max-width: 1000px;
                margin: 0 auto;
                background-color: white;
                padding: 30px;
                border-radius: 12px;
                box-shadow: 0 2px 12px rgba(0,0,0,0.1);
            }}
            .header {{
                margin-bottom: 30px;
                padding-bottom: 20px;
                border-bottom: 2px solid #eee;
            }}
            .header h2 {{
                margin: 0;
                color: #34495e;
            }}
            .upload-form {{ 
                margin-bottom: 30px;
                padding: 20px;
                background-color: #f8fafc;
                border-radius: 8px;
                border: 1px dashed #cbd5e0;
            }}
            .file-input-container {{
                display: flex;
                gap: 10px;
                align-items: center;
            }}
            input[type="file"] {{
                flex: 1;
                padding: 8px;
                border: 1px solid #e2e8f0;
                border-radius: 6px;
                background-color: white;
            }}
            .upload-btn {{
                padding: 8px 20px;
                background-color: #4299e1;
                color: white;
                border: none;
                border-radius: 6px;
                cursor: pointer;
                transition: background-color 0.2s;
            }}
            .upload-btn:hover {{
                background-color: #3182ce;
            }}
            .upload-btn:disabled {{
                background-color: #a0aec0;
                cursor: not-allowed;
            }}
            .progress-container {{
                display: none;
                margin-top: 10px;
            }}
            .progress-bar {{
                height: 20px;
                background-color: #edf2f7;
                border-radius: 10px;
                overflow: hidden;
            }}
            .progress {{
                width: 0%;
                height: 100%;
                background-color: #48bb78;
                transition: width 0.3s ease;
            }}
            .progress-details {{
                display: flex;
                justify-content: space-between;
                font-size: 12px;
                color: #666;
                margin-top: 5px;
            }}
            .file-list {{
                margin-top: 20px;
            }}
            .file-item {{
                margin: 10px 0;
                transition: all 0.2s;
            }}
            .file-main {{
                display: flex;
                align-items: center;
                padding: 15px;
                background-color: #fff;
                border: 1px solid #e2e8f0;
                border-radius: 8px;
                transition: all 0.2s;
            }}
            .file-item:hover .file-main {{
                border-color: #4299e1;
                box-shadow: 0 2px 8px rgba(66, 153, 225, 0.1);
            }}
            .file-icon {{
                font-size: 24px;
                margin-right: 15px;
            }}
            .file-info {{
                flex: 1;
            }}
            .file-name {{
                font-size: 16px;
                margin-bottom: 4px;
            }}
            .file-name a {{
                color: #2d3748;
                text-decoration: none;
            }}
            .file-name a:hover {{
                color: #4299e1;
            }}
            .file-details {{
                display: flex;
                gap: 15px;
                font-size: 13px;
                color: #718096;
            }}
            .file-actions {{
                margin-left: 15px;
            }}
            .delete-btn {{
                padding: 6px 12px;
                background-color: #fff;
                color: #e53e3e;
                border: 1px solid #e53e3e;
                border-radius: 6px;
                text-decoration: none;
                font-size: 14px;
                transition: all 0.2s;
            }}
            .delete-btn:hover {{
                background-color: #e53e3e;
                color: white;
            }}
            .error {{
                color: #e53e3e;
                padding: 10px;
                border-radius: 6px;
                background-color: #fff5f5;
                border: 1px solid #feb2b2;
            }}
            @media (max-width: 768px) {{
                .container {{
                    padding: 15px;
                }}
                .file-details {{
                    flex-direction: column;
                    gap: 5px;
                }}
            }}
        </style>
    </head>
    <body>
        <div class="container">
            <div class="header">
                <h2>文件上传系统</h2>
            </div>
            <div class="upload-form">
                <form id="uploadForm" enctype="multipart/form-data">
                    <div class="file-input-container">
                        <input type="file" name="file" id="fileInput">
                        <button type="submit" class="upload-btn" id="uploadBtn">上传</button>
                    </div>
                    <div class="progress-container" id="progressContainer">
                        <div class="progress-bar">
                            <div class="progress" id="progress"></div>
                        </div>
                        <div class="progress-details">
                            <span id="progressText">0%</span>
                            <span id="speedText">0 KB/s</span>
                            <span id="remainingText">剩余时间: 计算中...</span>
                        </div>
                    </div>
                </form>
            </div>
            <div class="file-list">
                <h3>已上传文件</h3>
                {files_html}
            </div>
        </div>
        <script>
            class UploadProgress {{
                constructor() {{
                    this.startTime = 0;
                    this.uploadedSize = 0;
                    this.totalSize = 0;
                    this.lastUploadedSize = 0;
                    this.lastTime = 0;
                }}

                start(totalSize) {{
                    this.startTime = Date.now();
                    this.lastTime = this.startTime;
                    this.totalSize = totalSize;
                    this.uploadedSize = 0;
                    this.lastUploadedSize = 0;
                }}

                update(uploadedSize) {{
                    this.uploadedSize = uploadedSize;
                    const now = Date.now();
                    const timeDiff = (now - this.lastTime) / 1000;

                    if (timeDiff >= 0.1) {{
                        const sizeDiff = this.uploadedSize - this.lastUploadedSize;
                        const speed = sizeDiff / timeDiff;
                        const progress = (this.uploadedSize / this.totalSize) * 100;
                        const remaining = (this.totalSize - this.uploadedSize) / speed;

                        this.lastTime = now;
                        this.lastUploadedSize = this.uploadedSize;

                        return {{
                            progress: progress,
                            speed: this.formatSpeed(speed),
                            remaining: this.formatTime(remaining)
                        }};
                    }}
                    return null;
                }}

                formatSpeed(speed) {{
                    if (speed < 1024) return `${{speed.toFixed(1)}} B/s`;
                    if (speed < 1024 * 1024) return `${{(speed / 1024).toFixed(1)}} KB/s`;
                    return `${{(speed / (1024 * 1024)).toFixed(1)}} MB/s`;
                }}

                formatTime(seconds) {{
                    if (seconds === Infinity || isNaN(seconds)) return '计算中...';
                    if (seconds < 60) return `${{Math.round(seconds)}}秒`;
                    if (seconds < 3600) return `${{Math.round(seconds / 60)}}分钟`;
                    return `${{Math.round(seconds / 3600)}}小时`;
                }}
            }}

            document.getElementById('uploadForm').onsubmit = async function(e) {{
                e.preventDefault();
                
                const fileInput = document.getElementById('fileInput');
                const uploadBtn = document.getElementById('uploadBtn');
                const progressContainer = document.getElementById('progressContainer');
                const progress = document.getElementById('progress');
                const progressText = document.getElementById('progressText');
                const speedText = document.getElementById('speedText');
                const remainingText = document.getElementById('remainingText');
                
                if (!fileInput.files.length) {{
                    alert('请选择文件');
                    return;
                }}
                
                const file = fileInput.files[0];
                const formData = new FormData();
                formData.append('file', file);
                
                uploadBtn.disabled = true;
                progressContainer.style.display = 'block';
                
                const uploadProgress = new UploadProgress();
                uploadProgress.start(file.size);
                
                try {{
                    const upload_id = Date.now().toString();
                    const xhr = new XMLHttpRequest();
                    
                    xhr.upload.onprogress = function(e) {{
                        if (e.lengthComputable) {{
                            const result = uploadProgress.update(e.loaded);
                            if (result) {{
                                progress.style.width = `${{result.progress}}%`;
                                progressText.textContent = `${{Math.round(result.progress)}}%`;
                                speedText.textContent = result.speed;
                                remainingText.textContent = `剩余时间: ${{result.remaining}}`;
                            }}
                        }}
                    }};
                    
                    xhr.onload = function() {{
                        if (xhr.status === 200) {{
                            window.location.reload();
                        }} else {{
                            throw new Error('上传失败');
                        }}
                    }};
                    
                    xhr.onerror = function() {{
                        throw new Error('网络错误');
                    }};
                    
                    xhr.open('POST', `/Upload?upload_id=${{upload_id}}`, true);
                    xhr.send(formData);
                }} catch (error) {{
                    alert('上传失败: ' + error.message);
                    uploadBtn.disabled = false;
                    progressContainer.style.display = 'none';
                }}
            }};

            function autoRefresh() {{
                if (document.querySelector('.file-md5')) {{
                    if (document.querySelector('.file-md5').textContent.includes('计算中')) {{
                        setTimeout(() => {{
                            window.location.reload();
                        }}, 5000);
                    }}
                }}
            }}
            autoRefresh();
        </script>
    </body>
    </html>
    '''

@app.route('/Upload', methods=['POST'])
def upload_file():
    """处理文件上传"""
    upload_id = request.args.get('upload_id')
    if not upload_id:
        return jsonify({'error': '无效的上传ID'}), 400

    try:
        if 'file' not in request.files:
            return jsonify({'error': '没有选择文件'}), 400

        file = request.files['file']
        if file.filename == '':
            return jsonify({'error': '没有选择文件'}), 400
        
        progress_file = ProgressFileStorage(file, upload_id)
        filename = secure_filename_with_chinese(urllib.parse.unquote(file.filename))
        file_path = os.path.join(UPLOAD_FOLDER, filename)
        
        progress_file.save(file_path)
        
        if upload_id in upload_progress:
            del upload_progress[upload_id]
        
        md5_queue.put(file_path)
        return jsonify({'message': '上传成功'}), 200
    except ClientDisconnected:
        if upload_id in upload_progress:
            del upload_progress[upload_id]
        return jsonify({'error': '客户端断开连接'}), 400
    except Exception as e:
        if upload_id in upload_progress:
            del upload_progress[upload_id]
        return jsonify({'error': str(e)}), 500

@app.route('/delete/<filename>')
def delete_file(filename):
    """处理文件删除"""
    try:
        file_path = os.path.join(UPLOAD_FOLDER, filename)
        if os.path.exists(file_path):
            os.remove(file_path)
            cache = load_md5_cache()
            if filename in cache:
                del cache[filename]
                save_md5_cache(cache)
            return redirect('/')
        return '文件不存在 <br><a href="/">返回</a>'
    except Exception as e:
        return f'删除失败：{str(e)} <br><a href="/">返回</a>'

@app.route('/download/<filename>')
def download_file(filename):
    """处理文件下载"""
    try:
        return send_from_directory(UPLOAD_FOLDER, filename, as_attachment=True)
    except Exception as e:
        return f'下载失败：{str(e)} <br><a href="/">返回</a>'

if __name__ == '__main__':
    # 创建上传目录
    os.makedirs(UPLOAD_FOLDER, exist_ok=True)
    
    # 启动MD5计算工作线程
    md5_thread = threading.Thread(target=md5_worker)
    md5_thread.daemon = True
    md5_thread.start()
    
    # 扫描现有文件
    scan_existing_files()
    
    # Flask应用配置
    app.config.update(
        MAX_CONTENT_LENGTH=None,
        UPLOAD_MAX_MEMORY_SIZE=1024 * 1024 * 1024,  # 1GB
        DEBUG=False,
        THREADED=True
    )
    
    # 启动服务器
    app.run(host='0.0.0.0', port=81, threaded=True)
